package engine.network
{
	import engine.core.*;
	
	import flash.events.*;
	import flash.net.*;
	import flash.utils.*;
	
	public class RxNetMessageSocket
	{
		private var m_socket:Socket;
		private var m_state:int;
		private var m_messageId:int;
		private var m_messageLength:int;
		private var m_messageCallback:Function;
		private var m_connectCallback:Function;
		private var m_disconnectCallback:Function;
		private var m_queue:ByteArray;
		private var m_host:String;
		private var m_port:int;
		private var m_reconnect:Boolean;
		private var m_timer:Timer;
		private var m_messageTime:Number;
		private static const QUEUE_MAX:int = 1024;
		private static const INITIAL_TIMER_DELAY:Number = 500;
		private static const MAXIMUM_TIMER_DELAY:Number = 30000;
		private static var s_byteArray:ByteArray = new ByteArray();
		public static const s_networkEndianness:String = "bigEndian";
		private static const STATE_UNCONNECTED:int = 0;
		private static const STATE_CONNECTING:int = 1;
		private static const STATE_CONNECTED:int = 2;
		
		public function RxNetMessageSocket()
		{
			this.m_queue = new ByteArray();
			this.m_socket = new Socket();
			this.m_socket.timeout = 15000;
			this.m_socket.endian = s_networkEndianness;
			this.m_socket.addEventListener(Event.CLOSE, this.OnDisconnect);
			this.m_socket.addEventListener(Event.CONNECT, this.OnConnect);
			this.m_socket.addEventListener(IOErrorEvent.IO_ERROR, this.OnIOError);
			this.m_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.OnSecurityError);
			this.m_socket.addEventListener(ProgressEvent.SOCKET_DATA, this.OnSocketData);
			s_byteArray.endian = s_networkEndianness;
			this.m_state = STATE_UNCONNECTED;
			this.m_timer = new Timer(INITIAL_TIMER_DELAY, 1);
			this.m_timer.addEventListener(TimerEvent.TIMER_COMPLETE, this.OnTimerComplete);
		}
		
		public function Connect(host:String, port:int, reconnect:Boolean = true) : void
		{
			RxLog.Trace("RxNetMessageSocket.Connect()");
			if (this.m_state == STATE_UNCONNECTED)
			{
				this.Reset();
				this.m_host = host;
				this.m_port = port;
				this.m_reconnect = reconnect;
				this.m_state = STATE_CONNECTING;
				this.m_socket.connect(this.m_host, this.m_port);
			}
		}
		
		public function Disconnect() : void
		{
			RxLog.Trace("RxNetMessageSocket.Disconnect()");
			this.m_state = STATE_UNCONNECTED;
			this.Flush();
			this.Recycle();
			this.Reset();
			return;
		}
		
		public function Connected() : Boolean
		{
			return this.m_state == STATE_CONNECTED && this.m_socket.connected;
		}
		
		public function Connecting() : Boolean
		{
			return this.m_state == STATE_CONNECTING;
		}
		
		private function Recycle() : void
		{
			RxLog.Trace("RxNetMessageSocket.Recycle()");
			if (this.m_socket.connected)
			{
				this.m_socket.close();
			}
			return;
		}
		
		private function FlushQueue() : void
		{
			if (m_queue.length > 0)
			{
				this.m_socket.writeBytes(m_queue);
				m_queue.clear();
			}
		}
		
		public function Flush() : void
		{
			if (this.m_socket.connected)
			{
				try
				{
					this.FlushQueue();
					this.m_socket.flush();
				}
				catch (error:*)
				{
				}
			}
			return;
		}
		
		public function SendMessage(message:RxNetMessageBase) : void
		{
			var _loc_2:Number;
			var _loc_3:int = 0;
			var _loc_4:int = 0;
			var _loc_5:ByteArray = null;
			RxLog.Trace("RxNetMessageSocket.SendMessage()");
			if (this.m_socket.connected)
			{
				_loc_2 = RxDate.Now();
				_loc_3 = message.GetId();
				_loc_4 = message.GetBinarySize(_loc_2);
				_loc_5 = s_byteArray;
				_loc_5.clear();
				message.ToBinary(_loc_5, _loc_2);
				if (_loc_5.length == _loc_4)
				{
					if (this.m_queue.length + _loc_4 > QUEUE_MAX)
					{
						this.FlushQueue();
					}
					this.m_queue.writeShort(_loc_3);
					if (!message.IsFixedSize())
					{
						this.m_queue.writeShort(_loc_4);
					}
					this.m_queue.writeBytes(_loc_5);
				}
				else
				{
					RxLog.Error("message length mismatch: id=" + _loc_3 + " expected=" + _loc_4 + " serialized=" + _loc_5.length);
				}
				_loc_5.clear();
			}
		}
		
		public function SetMessageCallback(callback:Function) : void
		{
			this.m_messageCallback = callback;
		}
		
		public function SetConnectCallback(callback:Function) : void
		{
			this.m_connectCallback = callback;
		}
		
		public function SetDisconnectCallback(callback:Function) : void
		{
			this.m_disconnectCallback = callback;
		}
		
		private function OnConnect(event:Event) : void
		{
			RxLog.Trace("RxNetMessageSocket.OnConnect()");
			this.m_queue.clear();
			this.m_timer.reset();
			this.m_timer.delay = INITIAL_TIMER_DELAY;
			this.m_state = STATE_CONNECTED;
			if (this.m_connectCallback != null)
			{
				this.m_connectCallback();
			}
		}
		
		private function OnDisconnect(event:Event) : void
		{
			RxLog.Trace("RxNetMessageSocket.OnDisconnect()");
			this.m_queue.clear();
			var _loc_2:* = this.m_state;
			this.m_state = STATE_UNCONNECTED;
			var _loc_3:Boolean = true;
			if (this.m_disconnectCallback != null)
			{
				_loc_3 = this.m_disconnectCallback();
			}
			if (_loc_3 && _loc_2 != STATE_UNCONNECTED)
			{
				this.Reconnect();
			}
			this.Reset();
		}
		
		private function OnIOError(event:IOErrorEvent) : void
		{
			RxLog.Warning("socket I/O error");
			if (this.m_state == STATE_CONNECTING)
			{
				this.m_state = STATE_UNCONNECTED;
				this.Reconnect();
			}
		}
		
		private function OnSecurityError(event:SecurityErrorEvent) : void
		{
			RxLog.Warning("socket security error");
			if (this.m_state == STATE_CONNECTING)
			{
				this.m_state = STATE_UNCONNECTED;
				this.Reconnect();
			}
		}
		
		private function Error(info:String) : void
		{
			RxLog.Warning(info);
			this.Reset();
			this.Recycle();
		}
		
		private function OnSocketData(event:ProgressEvent) : void
		{
			var _loc_2:Class = null;
			var _loc_3:Class = null;
			var _loc_4:RxNetMessageBase = null;
			var _loc_5:ByteArray = null;
			RxLog.Trace("RxNetMessageSocket.OnSocketData()");
			while (true)
			{
				
				if (this.m_state == STATE_UNCONNECTED)
				{
					break;
				}
				if (this.m_messageId < 0 && this.m_socket.bytesAvailable >= 2)
				{
					this.m_messageId = this.m_socket.readUnsignedShort();
					_loc_2 = RxNetMessageBase.GetMessageClass(this.m_messageId);
					if (_loc_2 == null)
					{
						this.Error("Received message with unrecognized ID: " + this.m_messageId);
						return;
					}
					this.m_messageTime = RxDate.Now();
					if (_loc_2.fixedSize)
					{
						this.m_messageLength = _loc_2.binarySize;
					}
				}
				if (this.m_messageId >= 0 && this.m_messageLength < 0 && this.m_socket.bytesAvailable >= 2)
				{
					this.m_messageLength = this.m_socket.readUnsignedShort();
				}
				if (this.m_messageLength >= 0 && this.m_socket.bytesAvailable >= this.m_messageLength)
				{
					_loc_3 = RxNetMessageBase.GetMessageClass(this.m_messageId);
					_loc_4 = new _loc_3;
					_loc_5 = s_byteArray;
					_loc_5.clear();
					this.m_socket.readBytes(_loc_5, 0, this.m_messageLength);
					_loc_4.FromBinary(_loc_5, this.m_messageTime);
					_loc_5.clear();
					this.Reset();
					if (this.m_messageCallback != null)
					{
						this.m_messageCallback(_loc_4);
					}
					continue;
				}
				break;
			}
		}
		
		private function Reset() : void
		{
			this.m_messageId = -1;
			this.m_messageLength = -1;
		}
		
		private function Reconnect() : void
		{
			if (this.m_reconnect)
			{
				this.m_timer.reset();
				this.m_timer.start();
			}
		}
		
		private function OnTimerComplete(event:TimerEvent) : void
		{
			this.m_timer.delay = Math.min(this.m_timer.delay * 2, MAXIMUM_TIMER_DELAY);
			this.Connect(this.m_host, this.m_port);
		}
	}
}
